home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 08 - 1992 / 08.01 Apr⁄May 92 / ~3D Animation / C version / Tutor3D[C].c < prev   
Encoding:
C/C++ Source or Header  |  1992-04-23  |  13.1 KB  |  419 lines  |  [TEXT/KAHL]

  1. /*------------------------------------------------------
  2. #
  3. #    Program: Tutor3D™
  4. #
  5. #    Copyright © 1991 Lincoln Lydick
  6. #    All Rights Reserved.
  7. #
  8. #
  9. Include these libraries (for THINK C):
  10.     MacTraps
  11.     SANE
  12.  
  13. Note:     
  14.     The procedures "RotateObject()" and "Point2Screen()"
  15.     significantly slow this program because THINK C creates a
  16.     JSR to some extra glue code in order to multiply and divide
  17.     long words. Therefore both procs are written in assembly,
  18.     however the C equivalent is provided in comments above.
  19.     Simply replace the asm {} statement with the C code if you
  20.     prefer.
  21.  
  22. ------------------------------------------------------*/
  23.  
  24. #include     "SANE.h"
  25.  
  26. #define        kMaxObjects            6                    /*num. objects*/
  27. #define        kMinutes                4                    /*minutes per deqree*/
  28. #define        kProjDistance        450                /*distance to proj. plane*/
  29. #define        kWidth                    500                /*width of window*/
  30. #define        kHeight                    280                /*height of window*/
  31. #define        kMoveUpKey            0x100000    /*'q' key = move up*/
  32. #define        kMoveDnKey            0x200000    /*'w' key = move down*/
  33. #define        kOriginH                (kWidth/2)    /*center of window    */
  34. #define        kOriginV                (kHeight/2)    /*ditto    */
  35. #define        kMapRowBytes        (((kWidth+15)/16)*2)
  36. #define        kNotBWAlertNum    256                /*    ALRT id for monitor bit depth > 1 */
  37.  
  38. /*    Define macros so MoveTo() & LineTo() accept Points.*/
  39. #define        QuickMoveTo(pt) asm{move.l pt, gOffPort.pnLoc}
  40. #define        QuickLineTo(pt) asm{move.l pt, -(sp)}asm {_LineTo}
  41.  
  42. enum     ObjectType    {cube, pyramid};
  43. typedef struct {short    x, y, z;
  44. } Point3D;            /*    struct for a 3 dimensional point.*/
  45.  
  46. typedef    struct {
  47.     Point3D            pt3D;
  48.     short            angle, sine, cosine;
  49. } ViewerInfo;    /*    struct for viewer's position.*/
  50.  
  51. typedef    struct {    
  52.     enum             ObjectType    objType;
  53.     Point3D            pt3D;
  54.     short            angle, halfWidth, height;
  55.     Boolean            rotates, moves;
  56. } ObjectInfo;        /*    struct for an object.*/
  57.  
  58. ViewerInfo        gViewer;
  59. Point3D                gDelta;
  60. Point                    gMouse, gVertex[8];
  61. WindowPtr        gWindow;
  62. BitMap                gBitMap;
  63. GrafPort            gOffPort;
  64. Rect                    gVisRect, gWindowRect;
  65. ObjectInfo            gObject[kMaxObjects];
  66. short                gVelocity, gSineTable[(90*kMinutes)+1];
  67. KeyMap                gKeys;
  68.  
  69. /****************************************************/
  70. /*    
  71. /*    Assign parameters to a new object (a cube or pyramid).
  72. /*    
  73. /****************************************************/
  74. static void NewObject(short index, enum ObjectType    theType, short width, short height,
  75.     Boolean rotates, Boolean moves, short positionX, short positionY, short positionZ)
  76. {
  77.     register    ObjectInfo        *obj;
  78.     
  79.     obj = &gObject[index];
  80.     obj->angle = 0;
  81.     obj->objType = theType;
  82.     obj->halfWidth = width/2;
  83.     obj->height = height;
  84.     obj->rotates = rotates;
  85.     obj->moves = moves;
  86.     obj->pt3D.x = positionX;
  87.     obj->pt3D.y = positionY;
  88.     obj->pt3D.z = positionZ;
  89. }
  90.  
  91. /****************************************************/
  92. /*    
  93. /*    Initialize of all our globals, build the trig table, set up an
  94. /*    offscreen buffer, create a new window, and initialize all
  95. /*    the objects to be drawn.
  96. /*    
  97. /****************************************************/
  98. static void Initialize(void)
  99. {
  100.     extended        angle;
  101.     short            i;
  102.  
  103.     InitGraf(&thePort);
  104.     InitFonts();
  105.     InitWindows();
  106.     InitMenus();
  107.     TEInit();
  108.     InitDialogs(0L);
  109.     InitCursor();
  110.     FlushEvents(everyEvent, 0);
  111.     SetCursor(*GetCursor(crossCursor));
  112.  
  113.     if ((*(*GetMainDevice())->gdPMap)->pixelSize > 1)
  114.     {
  115.         InitCursor();
  116.         StopAlert(kNotBWAlertNum, NULL);    /* tell user to switch to B&W.*/
  117.         ExitToShell();
  118.     }
  119.  
  120.         /*    create a table w/ the values of sine from 0-90.*/
  121.     for (i=0, angle=0.0; i<=90*kMinutes; i++, angle+=0.017453292/kMinutes)    
  122.         gSineTable[i] = sin(angle)*1000;
  123.  
  124.          /*    give the viewer an initial direction and position…*/
  125.     gViewer.angle = gViewer.sine = gViewer.pt3D.x = gViewer.pt3D.y = 0;    
  126.     gViewer.cosine = 999;
  127.     gViewer.pt3D.z = 130;
  128.  
  129.         /*    create some objects (0 to kMaxObjects-1).*/
  130.     NewObject(0, cube, 120, 120, false, false, -150, 600, 0);    
  131.     NewObject(1, cube, 300, 300, true, false, -40, 1100, 60);
  132.     NewObject(2, cube, 40, 10, true, true, 0, 500, 0);
  133.     NewObject(3, pyramid, 160, 160, false, false, 200, 700, 0);
  134.     NewObject(4, pyramid, 80, -80, true, false, 200, 700, 240);
  135.     NewObject(5, pyramid, 60, 60, false, false, -40, 1100, 0);
  136.  
  137.     SetRect(&gBitMap.bounds, 0, 0, kWidth, kHeight);
  138.     SetRect(&gWindowRect, 6, 45, kWidth+6, kHeight+45);
  139.     SetRect(&gVisRect, -150, -150, 650, 450);
  140.     gWindow = NewWindow(0L, &gWindowRect, "\pTutor3D™", true, 0, (Ptr)-1, false, 0);
  141.  
  142.         /*    make an offscreen bitmap and port…*/
  143.     gBitMap.rowBytes = kMapRowBytes;
  144.     gBitMap.baseAddr = NewPtr(kHeight*kMapRowBytes);
  145.     OpenPort(&gOffPort);
  146.     SetPort(&gOffPort);
  147.     SetPortBits(&gBitMap);
  148.     PenPat(white);
  149. }
  150.  
  151. /****************************************************/
  152. /*    
  153. /*    Return the sine and cosine values for an angle.
  154. /*    
  155. /****************************************************/
  156. static void GetTrigValues(register short *angle, register short *sine, register short *cosine)
  157. {
  158.     if (*angle >= 360*kMinutes)
  159.         *angle -= 360*kMinutes;
  160.     else if (*angle < 0)
  161.         *angle += 360*kMinutes;
  162.  
  163.     if (*angle <= 90*kMinutes)
  164.         {    *sine = gSineTable[*angle];
  165.             *cosine = gSineTable[90*kMinutes - *angle];
  166.         }
  167.     else if (*angle <= 180*kMinutes)
  168.         {    *sine = gSineTable[180*kMinutes - *angle];
  169.             *cosine = -gSineTable[*angle - 90*kMinutes];
  170.         }
  171.     else if (*angle <= 270*kMinutes)
  172.         {    *sine = -gSineTable[*angle - 180*kMinutes];
  173.             *cosine = -gSineTable[270*kMinutes - *angle];
  174.         }
  175.     else
  176.         {    *sine = -gSineTable[360*kMinutes - *angle];
  177.             *cosine = gSineTable[*angle - 270*kMinutes];
  178. }        }
  179.  
  180. /****************************************************/
  181. /*    
  182. /*    Increment an objects angle and find the sine and cosine
  183. /*    values. If the object moves, assign a new x,y position for
  184. /*    it as well. Finally, rotate the object's base around the z
  185. /*    axis and translate it to the correct position based on delta.
  186. /*    
  187. /*    register    Point        *vertex; short i;
  188. /*    
  189. /*    for (i = 0; i < 4; i++)
  190. /*        {    vertex = &gVertex[i];    savedH = vertex->h;        
  191. /*            vertex->h=((long)savedH*cosine/1000 -
  192. /*                (long)vertex->v*sine/1000)+gDelta.x;
  193. /*            vertex->v=((long)savedH*sine/1000 +
  194. /*                (long)vertex->v*cosine/1000)+gDelta.y;
  195. /*        }
  196. /****************************************************/
  197. static void RotateObject(register ObjectInfo     *object)
  198. {
  199.     Point        tempPt;
  200.     short    sine, cosine;
  201.  
  202.     object->angle += (object->objType == pyramid) ? -8*kMinutes : 2*kMinutes;
  203.     GetTrigValues(&object->angle, &sine, &cosine);
  204.     if (object->moves)
  205.         {    object->pt3D.x += sine*20/1000;            /*[EQ.1]*/
  206.             object->pt3D.y += cosine*-20/1000;        /*[EQ.2]*/
  207.         }
  208.  
  209.     asm    {    moveq        #3, d2                            ;    loop counter
  210.                 lea            gVertex, a0                    ;    our array of points
  211.     loop:        move.l        (a0), tempPt                    ;    ie., tempPt = gVertex[i];
  212.                 move.w        cosine, d0
  213.                 muls            tempPt.h, d0                    ;    tempPt.h * cosine
  214.                 divs            #1000, d0                    ;    divide by 1000
  215.                 move.w        sine, d1
  216.                 muls            tempPt.v, d1                    ;    tempPt.v * sine
  217.                 divs            #1000, d1                    ;    divide by 1000
  218.                 sub.w        d1, d0                            ;    subtract the two
  219.                 add.w        gDelta.x, d0                    ;    now translate x
  220.                 move.w        d0, OFFSET(Point, h)(a0);    save new h
  221.  
  222.                 move.w        sine, d0
  223.                 muls            tempPt.h, d0                    ;    tempPt.h * sine
  224.                 divs            #1000, d0                    ;    divide by 1000
  225.                 move.w        cosine, d1
  226.                 muls            tempPt.v, d1                    ;    tempPt.v * cosine
  227.                 divs            #1000, d1                    ;    divide by 1000
  228.                 add.w        d1, d0                            ;    add em up
  229.                 add.w        gDelta.y, d0                    ;    now translate y
  230.                 move.w        d0, OFFSET(Point, v)(a0);    save new v
  231.                 addq.l        #4, a0                            ;    next vertex address
  232.                 dbra            d2, @loop                        ;    loop
  233.         }
  234. }
  235.  
  236. /****************************************************/
  237. /*
  238. /*    Rotate a point around the z axis and find it's location in 2d
  239. /*    space using 2pt perspective.
  240. /*
  241. /*    saved = pt->h;        /*    saved is defined as a short.*/
  242. /*    pt->h = (long)saved*gViewer.cosine/1000 -
  243. /*        (long)pt->v*gViewer.sine/1000;            /*[EQ.6]*/
  244. /*    pt->v = (long)saved*gViewer.sine/1000 +
  245. /*        (long)pt->v*gViewer.cosine/1000;        /*[EQ.7]*/
  246. /*        /*[EQ.8 & 9]*/
  247. /*    if ((saved = pt->v) <= 0)    saved = 1;/*never <= 0*/
  248. /*    pt->h = (long)pt->h*kProjDistance/saved+kOriginH;
  249. /*    pt->v = (long)gDelta.z*kProjDistance/saved+kOriginV;
  250. /*    
  251. /****************************************************/
  252. static void Point2Screen(register Point *pt)
  253. {    asm    {        
  254.                 move.w        gViewer.cosine, d0        ;    [EQ.6]
  255.                 muls            OFFSET(Point, h)(pt), d0;    pt.h * cosine
  256.                 divs            #1000, d0                    ;    divide by 1000
  257.                 move.w        gViewer.sine, d1
  258.                 muls            OFFSET(Point, v)(pt), d1;    pt.v * sine
  259.                 divs            #1000, d1                    ;    divide by 1000
  260.                 sub.w        d1, d0                            ;    subtract, yields horizontal
  261.                 move.w        gViewer.sine, d1            ;    [EQ.7]
  262.                 muls            OFFSET(Point, h)(pt), d1;    pt.h * sine
  263.                 divs            #1000, d1                    ;    divide by 1000
  264.                 move.w        gViewer.cosine, d2
  265.                 muls            OFFSET(Point, v)(pt), d2;    pt.v * cosine
  266.                 divs            #1000, d2                    ;    divide by 1000
  267.                 add.w        d2, d1                            ;    add, yields vertical
  268.                 bgt            @project                        ;    if (vertical<=0)…
  269.                 moveq        #1, d1                            ;    then vertical=1
  270.  
  271. project:    muls            #kProjDistance, d0        ;    [EQ.8]. horiz*kProjDist
  272.                 divs            d1, d0                            ;    divide by the vertical
  273.                 addi.w        #kOriginH, d0                ;    add origin.h
  274.                 move.w        d0, OFFSET(Point, h)(pt);    save the new horizontal
  275.                 move.w        #kProjDistance, d0        ;    [EQ.9]
  276.                 muls            gDelta.z, d0                    ;    height * kProjDistance
  277.                 divs            d1, d0                            ;    divide by the vertical
  278.                 addi.w        #kOriginV, d0                ;    add origin.v
  279.                 move.w        d0, OFFSET(Point, v)(pt);    save the new vertical
  280.         }
  281. }
  282.  
  283. /****************************************************/
  284. /*    
  285. /*    For all of our cubes and pyramids, index thru each -
  286. /*    calculate sizes, translate, rotate, check for visibility,
  287. /*    and finally draw them.
  288. /*    
  289. /****************************************************/
  290. static void DrawObjects(void)
  291. {
  292.     register ObjectInfo     *obj;
  293.     short    i;
  294.  
  295.     for (i = 0; i < kMaxObjects; i++)
  296.         {    obj = &gObject[i];
  297.             gDelta.x = obj->pt3D.x - gViewer.pt3D.x;    /*[EQ.3]*/
  298.             gDelta.y = obj->pt3D.y - gViewer.pt3D.y;    /*[EQ.4]*/
  299.             gDelta.z = gViewer.pt3D.z - obj->pt3D.z ;    /*[EQ.5]*/
  300.  
  301.             if (obj->rotates)    /*    does this one rotate?*/
  302.                 {    gVertex[0].h=gVertex[0].v=gVertex[1].v=gVertex[3].h = -obj->halfWidth;
  303.                     gVertex[1].h=gVertex[2].h=gVertex[2].v=gVertex[3].v = obj->halfWidth;
  304.                     RotateObject(obj);
  305.                 }
  306.             else                        /*    translate*/
  307.                 {    gVertex[0].h = gVertex[3].h = -obj->halfWidth + gDelta.x;
  308.                     gVertex[0].v = gVertex[1].v = -obj->halfWidth + gDelta.y;
  309.                     gVertex[1].h = gVertex[2].h = obj->halfWidth + gDelta.x;
  310.                     gVertex[2].v = gVertex[3].v = obj->halfWidth + gDelta.y;
  311.                 }
  312.  
  313.             if (obj->objType == pyramid)    /*    a pyramid?*/
  314.                 {    gVertex[4].h = gDelta.x;    /*    assign apex*/
  315.                     gVertex[4].v = gDelta.y;
  316.                 }
  317.             else
  318.                 {    gVertex[4] = gVertex[0];    /*    top of cube.*/
  319.                     gVertex[5] = gVertex[1];
  320.                     gVertex[6] = gVertex[2];
  321.                     gVertex[7] = gVertex[3];
  322.                 }
  323.  
  324.             Point2Screen(&gVertex[0]);        /*    rotate & plot base*/
  325.             Point2Screen(&gVertex[1]);
  326.             Point2Screen(&gVertex[2]);
  327.             Point2Screen(&gVertex[3]);
  328.             gDelta.z -= obj->height;
  329.             Point2Screen(&gVertex[4]);
  330.  
  331.             if (! PtInRect(gVertex[4], &gVisRect))    /*    visible?*/
  332.                 continue;
  333.  
  334.             QuickMoveTo(gVertex[0]);
  335.             QuickLineTo(gVertex[1]);
  336.             QuickLineTo(gVertex[2]);
  337.             QuickLineTo(gVertex[3]);
  338.             QuickLineTo(gVertex[0]);
  339.             QuickLineTo(gVertex[4]);
  340.  
  341.             if (obj->objType == pyramid)
  342.                 {    QuickLineTo(gVertex[1]);        /*    Finish pyramid.*/
  343.                     QuickMoveTo(gVertex[2]);
  344.                     QuickLineTo(gVertex[4]);
  345.                     QuickLineTo(gVertex[3]);
  346.                 } else {
  347.                     Point2Screen(&gVertex[5]);    /*    Finish cube.*/
  348.                     Point2Screen(&gVertex[6]);
  349.                     Point2Screen(&gVertex[7]);
  350.                     QuickLineTo(gVertex[5]);
  351.                     QuickLineTo(gVertex[6]);
  352.                     QuickLineTo(gVertex[7]);
  353.                     QuickLineTo(gVertex[4]);
  354.                     QuickMoveTo(gVertex[1]);
  355.                     QuickLineTo(gVertex[5]);
  356.                     QuickMoveTo(gVertex[2]);
  357.                     QuickLineTo(gVertex[6]);
  358.                     QuickMoveTo(gVertex[3]);
  359.                     QuickLineTo(gVertex[7]);
  360. }        }        }
  361.  
  362. /****************************************************/
  363. /*    
  364. /*    Check mouse position (velocity is vertical movement,
  365. /*    rotation is horiz.), calculate the sine and cosine values of
  366. /*    the angle, and update the viewer's position. Finally, check
  367. /*    the keyboard to see if we should move up or down.
  368. /*    
  369. /****************************************************/
  370. static void GetViewerPosition(void)
  371. {
  372.     GetMouse(&gMouse);
  373.     if (! PtInRect(gMouse, &gWindowRect))
  374.         return;
  375.     gVelocity = -(gMouse.v-(kOriginV+45))/5;
  376.     gViewer.angle += (gMouse.h-(kOriginH+6))/14;
  377.     GetTrigValues(&gViewer.angle, &gViewer.sine, &gViewer.cosine);
  378.  
  379.     gViewer.pt3D.x += gViewer.sine*gVelocity/1000;    /*[EQ.1]*/
  380.     gViewer.pt3D.y += gViewer.cosine*gVelocity/1000;    /*[EQ.2]*/
  381.  
  382.     GetKeys(&gKeys);
  383.     if (gKeys[0] == kMoveUpKey)
  384.         gViewer.pt3D.z += 5;
  385.     if (gKeys[0] == kMoveDnKey)
  386.         gViewer.pt3D.z -= 5;
  387. }
  388.  
  389. /****************************************************/
  390. /*    
  391. /*    Draw a simple crosshair at the center of the window.
  392. /*    
  393. /****************************************************/
  394. static void DrawCrossHair(void)
  395. {
  396.     QuickMoveTo(#0x008200fa);    /*ie., MoveTo(250, 130)*/
  397.     QuickLineTo(#0x009600fa);        /*ie., LineTo(250, 150)*/
  398.     QuickMoveTo(#0x008c00f0);    /*ie., MoveTo(240, 140)*/
  399.     QuickLineTo(#0x008c0104);        /*ie., LineTo(260, 140)*/
  400. }
  401.  
  402. /****************************************************/
  403. /*    
  404. /*    Main event loop - initialize & cycle until the mouse
  405. /*    button is pressed.
  406. /*    
  407. /****************************************************/
  408. void main(void)
  409. {
  410.     Initialize();
  411.     while (! Button())
  412.         {    FillRect(&gBitMap.bounds, black);
  413.             GetViewerPosition();
  414.             DrawObjects();        /*    main pipeline*/
  415.             DrawCrossHair();
  416.             CopyBits(&gBitMap, &gWindow->portBits, &gBitMap.bounds, &gBitMap.bounds, 0, 0L);
  417.         }
  418.     FlushEvents(mDownMask+keyDownMask, 0);
  419. }